package cn.com.bluemoon.common.handler;


import cn.com.bluemoon.daps.api.model.RemoteDataModelService;
import cn.com.bluemoon.daps.api.model.RemoteModeUserAccreditService;
import cn.com.bluemoon.common.KeyAnnotationClz;
import cn.com.bluemoon.common.UserPermission;
import cn.com.bluemoon.daps.common.datascope.config.AuthWebConfig;
import cn.com.bluemoon.daps.common.datascope.log.holder.ProcessLogHolder;
import cn.com.bluemoon.daps.common.domain.BaseModel;
import cn.com.bluemoon.daps.common.domain.ResultBean;
import cn.com.bluemoon.daps.common.domain.UserInfoHolder;
import cn.com.bluemoon.daps.common.enums.BmStatus;
import cn.com.bluemoon.daps.common.exception.DapException;
import cn.com.bluemoon.daps.common.toolkit.BmAssetUtils;
import cn.com.bluemoon.daps.model.entity.DapDataModel;
import cn.com.bluemoon.daps.model.entity.DapDataModelUserAccredit;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestBody;

import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import static cn.com.bluemoon.daps.common.domain.ResultBean.UNAUTHORIZED;

/**
 * aop处理业务权限功能
 * “只允许对创建人操作”
 * <p>
 * 注：
 * <p>1.{@link cn.com.bluemoon.common.security.ControllerAop}的@Order(Integer.MAX_VALUE - 2) 确保比{@link ModelUserPermSingleDataAop}的order后，保证认证通过了才进行接口调用</p>
 * <p>2.确保{@link ModelUserPermSingleDataAop}的order 在拦截器{@link AuthWebConfig#addInterceptors(InterceptorRegistry)}的后
 * 保证{@link UserInfoHolder#getUserInfo()}已存在数据</p>
 * <p>3.确保拦截器{@link AuthWebConfig#addInterceptors(InterceptorRegistry)}最先执行，权限认证</p>
 *
 * @author Jarod Kong
 */
@Slf4j
@Aspect
@Component
@Order(Integer.MAX_VALUE - 3)
public class ModelUserPermSingleDataAop {
    @Autowired
    @Lazy
    private RemoteModeUserAccreditService userAccreditService;

    @Autowired
    private RemoteDataModelService modelService;

    @Pointcut("@annotation(cn.com.bluemoon.common.UserPermission.SingleRow)")
    public void userPermissionSingleRow() {
    }

    /**
     * 针对编辑 & 查看 & 删除 ；忽略新增的情况（目前只针对数据库中已存在的数据下，进行操作，不存在新增的情况）
     * 业务功能使用：只允许模型授权者对已存在的数据进行操作，其他用户不可以 （不考虑，所谓的层级、角色高低的情况）
     * 实现思路：
     * 基于自定义注解，针对http请求，进行解析
     * <p>
     * 获取@PathVariable值
     * 获取@RequestParam值
     * 获取@RequestBody属性值
     * 以上三种情况进行获取接口操作数据对应的创建人，
     * 基于当前请求的接口用户{@link UserInfoHolder#getUserInfoOrEmpty()}对应的用户与查询数据库返回的{@link BaseModel#getCreateBy()}
     * 进行匹配，若一致则允许操作，否则，返回{@link ResultBean#UNAUTHORIZED}
     * <p>
     * 注：
     * <p>
     * 基于{@link UserPermission.SingleRow#keyFormClz()}为{@link RequestBody}的情况下，
     * <p>
     * 存在以下情况：
     * <p>
     * <p>
     * 1） @RequestBody List xx || @RequestBody Set xx => Collection xx => xx.get(0)为{@link UserPermission.SingleRow#modelClz()}对应的Id值
     * <p>
     * 2） @RequestBody String [] xx => xx[0]为{@link UserPermission.SingleRow#modelClz()}对应的Id值
     * <p>
     * 3) @RequestBody Map xx => xx.get({@link UserPermission.SingleRow#key()})为{@link UserPermission.SingleRow#modelClz()}对应的Id值
     * <p>
     * 4）@RequestBody Object xx (实体类）=>必须实现接口{@link cn.com.bluemoon.daps.common.domain.UserPermissionApi#getUserPermissionKeyValue()}
     * 来指定请求体中哪个属性的值为{@link UserPermission.SingleRow#modelClz()}对应的Id值，增加接口的原因是
     * 开发定义一个实体vo有可能是嵌套多层的，导致反射获取，需要递归获取，麻烦、效率低下
     * <p>
     * 5）要是接口方法挂载了该注解，却无法找到对应的逻辑关系获取到id 则无权限操作
     * <p>
     *
     * @param pjp pjp
     * @return obj
     */
    @Around("userPermissionSingleRow()")
    public Object handlerControllerMethod(ProceedingJoinPoint pjp) throws Throwable {
        Method requestMethod = ((MethodSignature) pjp.getSignature()).getMethod();
        UserPermission.SingleRow userPerm = requestMethod.getAnnotation(UserPermission.SingleRow.class);
        if (userPerm != null && userPerm.enabled()) {
            try {
                String clzAndMethod = requestMethod.getDeclaringClass().getCanonicalName() + "." + requestMethod.getName();
                KeyAnnotationClz annotationClz = userPerm.keyFormClz();
                String modelId = annotationClz.getModelId(pjp);
                if (StringUtils.isBlank(modelId)) {
                    log.error("发现请求接口中截获的modelId无效，入参为空/null");
                    log.error("请求操作人是{}请求接口是{}, 获取模型{}的主键ID{}为空，不放行", UserInfoHolder.getAccount(), clzAndMethod, userPerm.modelClz().getCanonicalName(), userPerm.key());
                    throw new DapException("权限控制:" + userPerm.errorMsg(), UNAUTHORIZED.getCode());
                }
                ResultBean<DapDataModel> modelRb = modelService.getById(modelId);
                DapDataModel baseModel = modelRb.assetTrue("模型不存在，禁止访问");
                // 针对编辑 & 查看 & 删除 ；忽略新增的情况（目前只针对数据库中已存在的数据下，进行操作，不存在新增的情况）
                if (baseModel == null) {
                    log.error("发现请求接口中截获的model无效，请检查模型是否已被删除或者前端传递的值不正确！！");
                    log.error("请求操作人是{}请求接口是{}, 获取模型{}为空，不放行", UserInfoHolder.getAccount(), clzAndMethod, userPerm.modelClz().getCanonicalName());
                    throw new DapException("权限控制:" + userPerm.errorMsg(), UNAUTHORIZED.getCode());
                }
                if (!baseModel.getBmStatus().equals(BmStatus.ON)) {
                    log.error("发现请求接口中截获的model信息已被删除/禁用，请检查模型是否已被删除/禁用或者前端传递的值不正确！！");
                    throw new DapException("权限控制:" + userPerm.errorMsg(), UNAUTHORIZED.getCode());
                }
                ResultBean<Map<String, List<DapDataModelUserAccredit>>> listByModel = userAccreditService.listByModel();
                log.warn("获取模型下的用户授权信息情况，status:{}, msg:{}", listByModel.isOk(), listByModel.getMsg());
                BmAssetUtils.isTrue(listByModel.isOk(), listByModel.getMsg());
                Map<String, List<DapDataModelUserAccredit>> modelIdAndUserAccredit = listByModel.getContent();
                String account = UserInfoHolder.getAccount();
                boolean canOperated = modelIdAndUserAccredit.getOrDefault(modelId, Collections.emptyList())
                        .stream().anyMatch(d -> StringUtils.equals(d.getUserAccount(), account));
                log.info("请求操作人是{}请求接口是{}，该数据主键是{}，在模型下所属授权用户：{}", UserInfoHolder.getAccount(), clzAndMethod, modelId, canOperated);
                BmAssetUtils.isTrue(canOperated, () -> new DapException("权限控制:" + userPerm.errorMsg(), UNAUTHORIZED.getCode()));
                // 增加该接口必须是模型管理员的权限控制
                if (userPerm.needModelAdmin()) {
                    BmAssetUtils.isTrue(StringUtils.equals(account, baseModel.getModelAdmin()),
                            () -> new DapException("权限控制：只允许模型管理员进行操作！", UNAUTHORIZED.getCode()));
                }
            } catch (RuntimeException e){
                ProcessLogHolder.set(e.getMessage(), true, UserInfoHolder.getUserInfoOrEmpty());
                throw e;
            }
        }
        return pjp.proceed();
    }
}
